﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using Djs.Neuronic.Core;

namespace Djs.Neuronic.UI.Interactive
{
    #region InteractivePanel : Potomek DblPanel, který v sobě hostuje interaktivní prvek InteractiveArea a jeho Childs
    /// <summary>
    /// InteractivePanel : Potomek DblPanel, který v sobě hostuje 
    /// interaktivní prvek InteractiveArea a jeho Childs
    /// </summary>
    public class InteractivePanel : DblPanel
    {
        public InteractivePanel()
        {
            this.IntDesktop = new IntDesktop(this);
        }
        protected override void OnPaintToBuffer(object sender, PaintEventArgs e)
        {
            // base.OnPaintToBuffer(sender, e);
            this.IntDesktop.OnPaintToBuffer(this, e);
        }
        public IntDesktop IntDesktop { get; private set; }
        protected override void Dispose(bool disposing)
        {
            ((IDisposable)this.IntDesktop).Dispose();
        }
    }
    #endregion
    #region RelativeBounds : třída podporující relativní umístění objektu v rámci jeho parenta
    /// <summary>
    /// RelativeBounds : třída podporující relativní umístění objektu v rámci jeho parenta
    /// </summary>
    public class RelativeBounds
    {
        #region Konstrukce
        public RelativeBounds(IntArea owner)
        {
            this._Owner = owner;
            this._AnchorStyles = AnchorStyles.Left | AnchorStyles.Top;
            this._DimX = new RelativeDim();
            this._DimY = new RelativeDim();
        }
        private IntArea _Owner;
        private Size? ParentSize { get { return (this._Owner != null && this._Owner.Parent != null ? this._Owner.Parent.Bound.Size : (Size?)null); } }
        private AnchorStyles _AnchorStyles;
        /// <summary>
        /// Objekt řešící souřadnice na ose X
        /// </summary>
        private RelativeDim _DimX;
        /// <summary>
        /// Objekt řešící souřadnice na ose Y
        /// </summary>
        private RelativeDim _DimY;
        /// <summary>
        /// Uloží do sebe aktuální souřadnice (včetně pozice k parentu, pokud je znám)
        /// </summary>
        /// <param name="value"></param>
        private void _SetBound(Rectangle value)
        {
            Int32? parentWidth, parentHeight;
            this._GetParentSize(out parentWidth, out parentHeight);
            this._DimX.SetDim(value.X, value.Width, parentWidth);
            this._DimY.SetDim(value.Y, value.Height, parentHeight);
        }
        /// <summary>
        /// Vrátí aktuální souřadnice, se zohledněním Anchor a Parenta
        /// </summary>
        /// <returns></returns>
        private Rectangle _GetBound()
        {
            Int32? parentWidth, parentHeight;
            this._GetParentSize(out parentWidth, out parentHeight);

            int x, width, y, height;
            this._DimX.GetDim(this._AnchorStyles, AnchorStyles.Left, AnchorStyles.Right, parentWidth, out x, out width);
            this._DimY.GetDim(this._AnchorStyles, AnchorStyles.Top, AnchorStyles.Bottom, parentHeight, out y, out height);

            return new Rectangle(x, y, width, height);
        }
        /// <summary>
        /// Určí šířku a výšku parenta
        /// </summary>
        /// <param name="parentWidth"></param>
        /// <param name="parentHeight"></param>
        private void _GetParentSize(out int? parentWidth, out int? parentHeight)
        {
            parentWidth = (Int32?)null;
            parentHeight = (Int32?)null;
            Size? parentSize = this.ParentSize;
            if (parentSize.HasValue)
            {
                parentWidth = (Int32?)parentSize.Value.Width;
                parentHeight = (Int32?)parentSize.Value.Height;
            }
        }
        /// <summary>
        /// Pomocná třída pro evidenci souřadnic v jedné ose (X nebo Y)
        /// </summary>
        private class RelativeDim
        {
            private int _DistBegin;
            private int _Length;
            private int? _DistAfter;
            /// <summary>
            /// Uloží do sebe souřadnice na ose
            /// </summary>
            /// <param name="distBegin">Počátek objektu (X nebo Y)</param>
            /// <param name="length">Velikost objektu (Width nebo Height)</param>
            /// <param name="parentLength">Velikost parenta (Width nebo Height)</param>
            internal void SetDim(int distBegin, int length, int? parentLength)
            {
                this._DistBegin = distBegin;
                this._Length = length;
                this._DistAfter = (parentLength.HasValue ? (Int32?)(parentLength.Value - (distBegin + length)) : (Int32?)null);
            }
            /// <summary>
            /// Vrátí ze sebe souřadnice na ose
            /// </summary>
            /// <param name="anchor">Aktuální kotva</param>
            /// <param name="anchorBegin">Kotva definující Begin (Left nebo Top)</param>
            /// <param name="anchorAfter">Kotva definující After (Right nebo Bottom)</param>
            /// <param name="parentLength">Velikost parenta (Width nebo Height)</param>
            /// <param name="distBegin">Výstup: Počátek objektu (X nebo Y)</param>
            /// <param name="length">Výstup: Velikost objektu (Width nebo Height)</param>
            internal void GetDim(AnchorStyles anchor, AnchorStyles anchorBegin, AnchorStyles anchorAfter, int? parentLength, out int distBegin, out int length)
            {
                if (!parentLength.HasValue)
                {   // Pokud neznám svého parenta (jeho velikost), pak vrátím rozměry Begin a Length:
                    distBegin = this._DistBegin;
                    length = this._Length;
                }
                else
                {   // Znám parenta, můžu plnohodnotně pracovat s jeho rozměrem a s konkrétním anchor:
                    // Pokud teprve teď vidím parenta, ale dosud nemám uschovanou hodnotu this.DistAfter, pak si ji doplním:
                    if (!this._DistAfter.HasValue)
                        this._DistAfter = parentLength.Value - (this._DistBegin + this._Length);

                    bool isFixBegin = ((anchor & anchorBegin) != 0);
                    bool isFixAfter = !isFixBegin || ((anchor & anchorAfter) != 0);      // Pokud není fixovaný Begin, pak musí být End !!!

                    if (isFixBegin && !isFixAfter)
                    {   // Fix na Begin, ale ne na After:
                        distBegin = this._DistBegin;
                        length = this._Length;
                    }
                    else if (!isFixBegin && isFixAfter)
                    {   // Fix na After, ale ne na Begin:
                        distBegin = parentLength.Value - (this._DistAfter.Value + this._Length);
                        length = this._Length;
                    }
                    else
                    {   // Fix na Begin i After:
                        distBegin = this._DistBegin;
                        length = parentLength.Value - (this._DistAfter.Value + this._DistBegin);
                    }
                }
            }
        }
        #endregion
        #region Public property
        /// <summary>
        /// Umístění objektu v rámci jeho parenta.
        /// Setování uloží souřadnice do objektu včetně umístění vzhledem k velikosti parenta.
        /// Čtení hodnoty zhlední aktuální Anchor a aktuální velikost parenta.
        /// </summary>
        public Rectangle Bound { get { return this._GetBound(); } set { this._SetBound(value); } }
        /// <summary>
        /// Kotva objektu
        /// </summary>
        public AnchorStyles Anchor { get { return this._AnchorStyles; } set { this._AnchorStyles = value; } }
        #endregion
    }
    #endregion
    #region enum InteractiveMouseState, InteractiveMouseChangeType
    public enum InteractiveMouseState
    {
        None,
        MouseOver,
        MouseDownLeft,
        MouseDownRight,
        MouseDrag
    }
    public enum InteractiveMouseChangeType
    {
        None,
        MouseEnter,
        MouseLeave,
        MouseMove,
        MouseDownLeft,
        MouseUpLeft,
        MouseClickLeft,
        MouseClickRight,
        MouseDownRight,
        MouseUpRight,
        MouseDragBegin,
        MouseDragMove,
        MouseDragEnd
    }
    #endregion
    #region LinearColorBlender : Třída, která provádí míchání barev v jednorozměrném prostoru
    /// <summary>
    /// LinearColorBlender : Třída, která provádí míchání barev v jednorozměrném prostoru
    /// </summary>
    public class LinearColorBlender
    {
        public LinearColorBlender()
        {
            this._ColorPoints = new List<ColorBlendPoint>();
        }
        private List<ColorBlendPoint> _ColorPoints;
        /// <summary>
        /// Počet bodů
        /// </summary>
        public int Count { get { return this._ColorPoints.Count; } }
        /// <summary>
        /// Přidá do ColorBlenderu novou pozici a barvu na ní.
        /// Pokud na téže pozici již je záznam, pak barvu přepíše.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="color"></param>
        public void Add(float position, Color color)
        {
            int index = this._ColorPoints.FindIndex(p => p.Position == position);
            if (index >= 0)
            {   // Pokud pro danou pozici už mám záznam, pak jej aktualizuji (vložím do záznamu novou barvu):
                this._ColorPoints[index].Color = color;
            }
            else
            {   // Dosud neobsazená souřadnice = přidáme nový záznam:
                this._ColorPoints.Add(new ColorBlendPoint(position, color));
                if (this._ColorPoints.Count > 1)
                    this._ColorPoints.Sort(ColorBlendPoint.CompareByPosition);
            }
        }
        /// <summary>
        /// Smaže všechny pozice z ColorBlenderu
        /// </summary>
        public void Clear()
        {
            this._ColorPoints.Clear();
        }
        /// <summary>
        /// Z ColorBlenderu vrátí barvu z dané pozice. Exaktní pozice = vrátí zadanou barvu. Pozice mezi dvěma exaktně zadanými = vrátí aproximovanou barvu.
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        public Color Get(float position)
        {
            return this.Get(position, Color.Empty);
        }
        /// <summary>
        /// Z ColorBlenderu vrátí barvu z dané pozice. Exaktní pozice = vrátí zadanou barvu. Pozice mezi dvěma exaktně zadanými = vrátí aproximovanou barvu.
        /// </summary>
        /// <param name="position"></param>
        /// <returns></returns>
        public Color Get(float position, Color defaultColor)
        {
            // Exaktní pozice = vrátím exaktní barvu:
            ColorBlendPoint exact = this._ColorPoints.Find(p => p.Position == position);
            if (exact != null) return exact.Color;

            // Aproximace: základem je to, že pole barev je setříděné vzestupně podle Position:
            ColorBlendPoint prev = this._ColorPoints.LastOrDefault(p => p.Position < position);
            ColorBlendPoint next = this._ColorPoints.FirstOrDefault(p => p.Position > position);
            // V následujících případech neproběhne aproximace, protože jsme našli 0 nebo 1 sousední barvu:
            if (prev == null && next == null) return defaultColor;  // Žádná položka => default
            if (prev == null && next != null) return next.Color;    // Máme jen pozici next (její pozice > hledaná pozice, tedy hledám hodně vlevo), vrátím barvu z next
            if (prev != null && next == null) return prev.Color;    //  dtto vpravo.

            // Reálná aproximace (požadovaná pozice leží mezi dvěma reálnými pozicemi, obě jsou not null, pozice je mezi nimi a ne v některé z nich):
            float relative = (position - prev.Position) / (next.Position - prev.Position);        // Relativní pozice 0 - 1, mimo krajní body
            return Color.FromArgb(
                _GetApproximatelly(prev.Color.A, next.Color.A, relative),
                _GetApproximatelly(prev.Color.R, next.Color.R, relative),
                _GetApproximatelly(prev.Color.G, next.Color.G, relative),
                _GetApproximatelly(prev.Color.B, next.Color.B, relative));
        }
        private int _GetApproximatelly(byte a, byte b, float relative)
        {
            float r = (float)a + (relative * ((float)(b - a)));
            if (r < 0F) return 0;
            if (r > 255F) return 255;
            return (int)Math.Round(r, 0);
        }
        /// <summary>
        /// Třída obsahující pozici a její barvu
        /// </summary>
        protected class ColorBlendPoint
        {
            internal ColorBlendPoint(float position, Color color)
            {
                this.Position = position;
                this.Color = color;
            }
            internal static int CompareByPosition(ColorBlendPoint a, ColorBlendPoint b)
            {
                return a.Position.CompareTo(b.Position);
            }
            internal float Position { get; set; }
            internal Color Color { get; set; }
        }
    }
    #endregion
}
